package cmd

import (
	"flag"
	"io"
	"os"

	"gitee.com/youkelike/ziam/zapp/app"
	"gitee.com/youkelike/ziam/zctl/cmd/color"
	"gitee.com/youkelike/ziam/zctl/cmd/completion"
	"gitee.com/youkelike/ziam/zctl/cmd/jwt"
	"gitee.com/youkelike/ziam/zctl/cmd/new"
	"gitee.com/youkelike/ziam/zctl/cmd/option"
	"gitee.com/youkelike/ziam/zctl/cmd/policy"
	"gitee.com/youkelike/ziam/zctl/cmd/secret"
	"gitee.com/youkelike/ziam/zctl/cmd/user"
	"gitee.com/youkelike/ziam/zctl/cmd/validate"
	"gitee.com/youkelike/ziam/zctl/cmd/version"
	"gitee.com/youkelike/ziam/zctl/options"
	"gitee.com/youkelike/ziam/zctl/options/util"
	"gitee.com/youkelike/ziam/zctl/util/templates"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

func NewDefaultZCtlCommand() *cobra.Command {
	return NewZCtlCommand(os.Stdin, os.Stdout, os.Stderr)
}

func NewZCtlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
	// 根命令
	cmdRoot := &cobra.Command{
		Use:   "zctl",
		Short: "zctl controls the iam platform",
		Long: templates.LongDesc(`
		zctl controls the iam platform, is the client side tool for iam platform.`),
		PersistentPreRunE: func(*cobra.Command, []string) error {
			return initProfiling()
		},
		// 因为有多级子命令，所以设置单独运行当前命令只会输出帮助信息
		Run: runHelp,
		PersistentPostRunE: func(*cobra.Command, []string) error {
			return flushProfiling()
		},
	}

	// 拿到根命令的 flagset，对所有子命令都有效
	flags := cmdRoot.PersistentFlags()
	// 设置 flag 参数命名格式化方法
	flags.SetNormalizeFunc(app.WordSepNormalizeFunc) // Warn for "_" flags

	// 和监控有关的 flags
	addProfilingFlags(flags)

	// 下面 5 行代码用于使用 zsdk2，
	// 用到装饰器模式，通过对配置进行两层代理设置，把 zctl 的配置对象包装成 factoryImpl 对象
	// 构造 zctl 自己的配置，并提供转成 zsdk2 配置的方法 ToRESTConfig()
	iamConfigFlags := options.NewConfigFlags(true).WithDeprecatedPasswordFlag().WithDeprecatedSecretFlag()
	iamConfigFlags.AddFlags(flags)
	// 第一层代理：加一个是否检测版本匹配的 flag 和对应功能，如果不需要这个功能，可以去掉这一层
	// 做法是在自定义的 ToRESTConfig() 方法内部，先检查版本匹配，再调用代理的 ToRESTConfig() 方法
	matchVersionZiamConfigFlags := util.NewMatchVersionFlags(iamConfigFlags)
	matchVersionZiamConfigFlags.AddFlags(flags)
	// 第二层代理：代理 ToRESTConfig() 方法的同时，还添加了获取 zsdk2 客户端、REST 客户端的方法
	// 所有子命令都通过它来使用 zsdk2
	f := options.NewFactory(matchVersionZiamConfigFlags)

	// 这个写在调用 viper.Unmarshal() 之前就行，
	_ = viper.BindPFlags(flags)
	// 设置读取配置文件的方法
	cobra.OnInitialize(func() {
		app.LoadConfig(viper.GetString(options.FlagZctlConfig), "zctl")
	})
	// 加载 go 内置 flag 模块的设置
	flags.AddGoFlagSet(flag.CommandLine)

	ioStreams := options.IOStreams{In: in, Out: out, ErrOut: err}

	// 用 CommandGroups 结构组织一堆子命令
	groups := templates.CommandGroups{
		{
			Message: "Basic Commands:",
			Commands: []*cobra.Command{
				// 打印机器信息，在 windows 上不能用
				// info.NewCmdInfo(f, ioStreams),
				// 打印终端支持的颜色
				color.NewCmdColor(f, ioStreams),
				// 生成命令和子命令
				new.NewCmdNew(f, ioStreams),
				jwt.NewCmdJWT(f, ioStreams),
			},
		},
		{
			Message: "Identity and Access Management Commands:",
			Commands: []*cobra.Command{
				user.NewCmdUser(f, ioStreams),
				secret.NewCmdSecret(f, ioStreams),
				policy.NewCmdPolicy(f, ioStreams),
			},
		},
		{
			Message: "Troubleshooting and Debugging Commands:",
			Commands: []*cobra.Command{
				// 检查 apiserver 是否可以连通
				validate.NewCmdValidate(f, ioStreams),
			},
		},
		{
			Message: "Settings Commands:",
			Commands: []*cobra.Command{
				// 系统设置
				// set.NewCmdSet(f, ioStreams),
				// 生成自动补全脚本
				completion.NewCmdCompletion(ioStreams.Out, ""),
			},
		},
	}
	// 子命令添加到 cmds 中
	groups.AddTo(cmdRoot)

	filters := []string{"options"}
	templates.ActsAsRootCommand(cmdRoot, filters, groups...)

	// 这两个子命令比较特殊（不需要展示在命令 usage 信息里），单独添加
	cmdRoot.AddCommand(version.NewCmdVersion(f, ioStreams))
	cmdRoot.AddCommand(option.NewCmdOptions(ioStreams.Out))

	return cmdRoot
}

func runHelp(cmd *cobra.Command, args []string) {
	_ = cmd.Help()
}
